代码改变世界

路径寻找——八数码问题

2016-08-03 11:36  shuaihanhungry  阅读(672)  评论(0编辑  收藏  举报

路径寻找问题可以转换为隐式图的广度遍历,目的是找到一条从初始状态到最终状态的最优路径,它不同于回溯法那样找到一个符合某些要求的解(如N皇后问题,数独问题等),回溯法是在深度遍历中进行剪枝。还要注意的是路径寻找问题是图的广度遍历不是树的广度遍历,树的广度遍历不需要判重,而图的广度遍历是需要判重的,如果不判重,时间和空间很有可能会产生浪费。

八数码问题、倒水问题都是路径寻找问题。下面给出解决八数码问题的代码。注意它和树的BFS的异同,也注意其中使用的小技巧,如memcmp()和memcpy()。

#include<cstdio>
#include<cstring>
#include<unordered_set>
using namespace std;

typedef int State[9];
const int maxstate = 1000000;
State st[maxstate], goal;  //用st数组模拟队列,front为队首,rear为队尾,特殊情况可能会特别定义状态结构体并使用优先队列
int dist[maxstate]; //移动步数

const int dx[] = {-1, 1, 0, 0};
const int dy[] = {0, 0, -1, 1};

unordered_set<int> vis; //C++11,底次使用hash_table,而不是像set使用rb-tree

void init_lookup_table() {
    vis.clear();
}

int try_to_insert(int s) {
    int v = 0;
    for(int i = 0; i < 9; i++) v = v * 10 + st[s][i];
    if(vis.count(v)) return 0;
    vis.insert(v);
    return 1;
}

int BFS() {
    init_lookup_table(); //判重使用
    int front = 1, rear = 2;  //front表示步数,rear表示最后一个添加进去的状态所在的编号
    while(front < rear) {
        State& s = st[front];
        if(memcmp(goal, s, sizeof(s)) == 0) return front;  //找到目标
        int z;
        for(z = 0; z < 9; z++) if(!s[z]) break; //找到0的位置,也就是需要移动的格子(空白块)的位置
        
        int x = z / 3, y = z % 3; //获取行列编号
        for(int d = 0; d < 4; d++) {
            int newx = x + dx[d];
            int newy = y + dy[d];
            int newz = newx * 3 + newy; //新的空白块的位置
            if(newx >= 0 && newx < 3 && newy >= 0 && newy < 3) { //如果移动合法
                State& t = st[rear];
                memcpy(&t, &s, sizeof(s)); //扩展新结点
                t[newz] = s[z];
                t[z] = s[newz];
                dist[rear] = dist[front] + 1;  //更新移动步数
                if(try_to_insert(rear)) rear++;
            }
        }
        front++;  //更新移动步数
    }
    return 0;
}

int main() {
    for(int i = 0; i < 9; i++) scanf("%d", &st[1][i]);
    for(int i = 0; i < 9; i++) scanf("%d", &goal[i]);
    int ans = BFS();
    if(ans > 0) printf("%d\n", dist[ans]); //最少移动多少步
    else printf("-1\n");
    return 0;
}

/*
输入:
2 6 4 1 3 7 0 5 8
8 1 5 7 3 6 4 0 2
输出:
31
*/